package com.example.demo.excel;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.example.demo.exception.BusinessException;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import com.example.demo.excel.valid.ExcelImportValid;
import com.example.demo.excel.valid.ExcelValidException;
import com.example.demo.dto.ExcelImportCommonDto;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @title 导入excel监听
 * @author yg.zhao
 * @description 
 * @date 2022/12/12
 * @param 
 * @return 
*/
@Slf4j
public class ImportExcelDataListener<T extends ExcelImportCommonDto> extends AnalysisEventListener<T> {
    /**
     * 防止OOM，放入缓存list
     */
    private static final int LIST_CACHE = 2000;

    /**
     * excel限制导入条数
     */
    private Integer rowNumLimit = 2000;

    /**
     * service的名称
     */
    private final String serviceName;

    /**
     * 校验通过数据处理方法
     */
    private final String accessMethodName;

    /**
     * 异常数据处理
     */
    private final String errorMethodName;

    @Getter
    public final String version;

    private Map<String, Object> paramMap = new HashMap<>();

    public ImportExcelDataListener(String serviceName, Map<String, Object> param) {
        this.serviceName = serviceName;
        this.accessMethodName = "savaSuccessForExcel";
        this.errorMethodName = "saveErrorForExcel";
        version = UUID.randomUUID().toString();
        paramMap = param;
    }

    public ImportExcelDataListener(String serviceName) {
        this.serviceName = serviceName;
        this.accessMethodName = "savaSuccessForExcel";
        this.errorMethodName = "saveErrorForExcel";
        version = UUID.randomUUID().toString();
        paramMap = new HashMap<>();
    }

    public ImportExcelDataListener(String serviceName, Integer rowNumLimit) {
        this.serviceName = serviceName;
        this.rowNumLimit = rowNumLimit;
        this.accessMethodName = "savaSuccessForExcel";
        this.errorMethodName = "saveErrorForExcel";
        version = UUID.randomUUID().toString();
        paramMap = new HashMap<>();
    }

    /**
     * 校验通过数据
     */
    private final List<T> successList = new LinkedList<>();

    /**
     * 异常数据  （key：行数，value：错误原因）
     */
    private final Map<Integer, String> errorMap = new HashMap<>();

    /**
     * <p>所有数据解析完成后调用此方法</p>
     */
    @SneakyThrows
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) throws ExcelValidException {
        //保存数据，确保最后的遗留数据也能保存到数据库或者进行其他操作
        this.execute();
        if (errorMap.size() > 0) {
            this.executeError();
        }
    }

    /**
     * <p>数据转换异常时的处理，比如required是Integer，provided是"sss"的字符串</p>
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        exception.printStackTrace();
        if (exception instanceof ExcelDataConvertException) {
            Integer columnIndex = ((ExcelDataConvertException) exception).getColumnIndex() + 1;
            Integer rowIndex = ((ExcelDataConvertException) exception).getRowIndex() + 1;
            String message = String.format("第%s行，第%s列数据格式有误，请核实", rowIndex, columnIndex);
            throw new RuntimeException(message);
        } else {
            super.onException(exception, context);
        }
    }

    @Override
    public void invoke(T data, AnalysisContext analysisContext) {
        Integer rowNumber = analysisContext.readSheetHolder().getApproximateTotalRowNumber();
        if (rowNumber == 0) {
            throw new BusinessException("导入数据为空");
        }
        Integer dataRowNum = rowNumber - 1;
        // 判断是否超过限制
        if (dataRowNum.compareTo(rowNumLimit) > 0) {
            throw new BusinessException("导入超过限制，最多支持" + rowNumLimit + "条数据");
        }

        ReadRowHolder readRowHolder = analysisContext.readRowHolder();
        int rowIndex =  readRowHolder.getRowIndex() + 1;
        data.setRowIndex(rowIndex);
        String errorMsg = ExcelImportValid.valid(data);
        if (StringUtils.isNotEmpty(errorMsg)) {
            errorMap.put(rowIndex, errorMsg);
            return;
        }
        //将解析完的数据加入到list中
        successList.add(data);
        // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
        if (successList.size() >= LIST_CACHE) {
            try {
                this.execute();
            } catch (Exception e) {
                e.printStackTrace();
                log.error("执行导入解析方法异常#" + e);
            }
            // 存储完成清理 list
            successList.clear();
        }

    }

    /**
     * 执行数据保存的操作
     * @throws Exception
     */
    public void execute() throws Exception {
        try {
            Object beanService = SpringUtil.getBean(serviceName + "Impl");
            Class<?> beanClass = beanService.getClass();
            Method method = beanClass.getMethod(accessMethodName, List.class, String.class, Map.class);
            method.invoke(beanService, successList, version, paramMap);
        } catch (NoSuchMethodException | IllegalAccessException e) {
            log.warn("import error:", e);
            throw new RuntimeException("导入执行保存失败");
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            throw new ExcelValidException("有异常数据");
        }
    }

    /**
     * 执行错误数据处理的操作
     */
    public void executeError() {
        try {
            Object beanService = SpringUtil.getBean(serviceName + "Impl");
            Class<?> beanClass = beanService.getClass();
            Method method = beanClass.getMethod(errorMethodName, Map.class, String.class);
            method.invoke(beanService, errorMap, version);
        } catch (NoSuchMethodException | IllegalAccessException e) {
            log.warn("execute error:", e);
            throw new RuntimeException("处理错误数据失败");
        } catch (InvocationTargetException e) {
            throw new ExcelValidException("有异常数据");
        }
    }
}

